package com.ycorn.auth.config;

import com.ycorn.auth.security.JdbcUserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
 * @Author: wujianmin
 * @Date: 2020/4/8 10:18
 * @Function:
 * @Version 1.0
 */
@EnableAuthorizationServer
@Configuration
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
    /**
     * 配置客户端详情服务(ClientDetailService),客户端详情信息在这里进行初始化
     * 能够把客户端的详细信息写死在这里或者通过数据库查询获取详情信息
     * <p>
     * ClientDetailsServiceConfigurer 能够使用内存或者JDBC来实现客户端详情服务（ClientDetailsService），
     * ClientDetailsService负责查找ClientDetails，而ClientDetails有几个重要的属性如下列表：
     * clientId：（必须的）用来标识客户的Id。
     * secret：（需要值得信任的客户端）客户端安全码，如果有的话。
     * scope: 用来限制客户端的访问范围，如果为空（默认）的话，那么客户端拥有全部的访问范围。
     * resourceIds:
     * authorizedGrantTypes：此客户端可以使用的授权类型，默认为空。
     * authorities：此客户端可以使用的权限（基于Spring Security authorities）。
     * 客户端详情（Client Details）能够在应用程序运行的时候进行更新，可以通过访问底层的存储服务（例如将客户
     * 端详情存储在一个关系数据库的表中，就可以使用 JdbcClientDetailsService）或者通过自己实现
     * ClientRegistrationService接口（同时你也可以实现 ClientDetailsService 接口）来进行管理
     * <p>
     * 核心 客户端详情的 ClientId Secret(需要BCrypt加密) authorizedGrantType 授权类型 resourceIds访问的资源Ids
     * redirectUris 回调地址 scopes 任意标识用于客户端访问范围的验证
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("c1") // 客户端Id
                .secret(new BCryptPasswordEncoder().encode("secret")) //客户端安全秘钥
                .resourceIds("order") // 绑定的资源id
                .scopes("all") // scopes // 允许的授权范围
                .authorizedGrantTypes("authorization_code",
                        "password", "client_credentials", "implicit", "refresh_token")// 该client允许的授权类型 authorization_code授权码, password账号密码, refresh_token, implicit 隐式, client_credentials 客户端认证
                .autoApprove(false) //是否自动放行
                .redirectUris("/getAccessCode"); //加上验证回调地址
    }


    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    /**
     * 配置tokenService 令牌服务
     * 定义AuthorizationServerTokenServices在AuthorizationServer中定义AuthorizationServerTokenServices
     *
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service = new DefaultTokenServices();
        service.setTokenStore(tokenStore); //设置令牌存储模式
        service.setClientDetailsService(clientDetailsService); // 设置客户端服务详情
        service.setSupportRefreshToken(true); //是否支持刷新token
        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期 2小时
        service.setAccessTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        //设置授权码模式的授权码如何存取，暂时采用内存方式
        return new InMemoryAuthorizationCodeServices();
    }

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JdbcUserDetailServiceImpl jdbcUserDetailService;

    /**
     * 配置 令牌的访问端点(token) 和 令牌服务(token services)
     * 配置认证管理器
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager) // 设置authenticationManager 认证管理器，当你选择了资源所有者密码（password）授权类型的时候，请设置  这个属性注入一个 AuthenticationManager 对象
                .authorizationCodeServices(authorizationCodeServices) // 这个属性是用来设置授权码服务的（即 AuthorizationCodeServices 的实例对象），主要用于 "authorization_code" 授权码类型模式
                .tokenServices(tokenService())
                .userDetailsService(jdbcUserDetailService) // 设置userDetailService 登录验证
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }


    /**
     * 配置令牌端点的访问安全约束
     * 用来配置令牌端点(Token Endpoint)的安全约束
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()") //tokenkey这个endpoint当使用JwtToken且使用非对称加密时，资源服务用于获取公钥而开放的，这里指这个endpoint完全公开。
                .checkTokenAccess("permitAll()") //checkToken这个endpoint完全公开
                .allowFormAuthenticationForClients(); // 允许表单认证

    }
}
